// Copyright (c) Appeon
// The following has been modified by Appeon under compliance with the Apache 2.0 license from the original work

// Copyright(c) 2002-2019, Npgsql
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written agreement
// is hereby granted, provided that the above copyright notice and this
// paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL NPGSQL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// Npgsql HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// NPGSQL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE.THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND Npgsql
// HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
// OR MODIFICATIONS.

using System;
using Npgsql;
using NpgsqlTypes;
using System.Data.Common;
using JetBrains.Annotations;
using SnapObjects.Commons;

namespace SnapObjects.Data.PostgreSql
{
    public class PostgreSqlStringTypeMapping : PostgreSqlTypeMapping
    {
        private readonly int _maxSpecificSize;

        public PostgreSqlStringTypeMapping([NotNull] string dbTypeName, NpgsqlDbType npgsqlDbType)
            : base(dbTypeName, typeof(string), npgsqlDbType)
        {
            this.NpgsqlDbType = npgsqlDbType;
        }

        private NpgsqlDbType NpgsqlDbType { get; }


        /// <summary>
        ///     This API supports the Entity Framework Core infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public override TypeMapping Clone(string dbTypeName, int? size)
        {
            return new PostgreSqlStringTypeMapping(dbTypeName, this.NpgsqlDbType);
        }

        /// <summary>
        ///     This API supports the Entity Framework Core infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        protected override void ConfigureParameter(DbParameter parameter, IAdoDbParameter adoDbParameter)
        {
            // For strings and byte arrays, set the max length to the size facet if specified, or
            // 8000 bytes if no size facet specified, if the data will fit so as to avoid query cache
            // fragmentation by setting lots of different Size values otherwise always set to
            // -1 (unbounded) to avoid SQL client size inference.

            if (parameter is NpgsqlParameter npgsqlParameter)
            {
                npgsqlParameter.NpgsqlDbType = this.NpgsqlDbType;
            }

            var value = parameter.Value;
            var length = (value as string)?.Length ?? (value as byte[])?.Length;

            parameter.Size = value == null || value == DBNull.Value || (length != null && length <= _maxSpecificSize)
                ? _maxSpecificSize
                : -1;
        }

        /// <summary>
        ///     Generates the SQL representation of a literal value.
        /// </summary>
        /// <param name="value">The literal value.</param>
        /// <returns>
        ///     The generated string.
        /// </returns>
        protected override string GenerateNonNullSqlLiteral(object value)
        {
            return this.IsUnicode
                ? $"N'{this.EscapeSqlLiteral((string)value)}'" // Interpolation okay; strings
                : $"'{this.EscapeSqlLiteral((string)value)}'";
        }

        private string EscapeSqlLiteral([NotNull] string literal)
        {
            return Check.NotNull(literal, nameof(literal)).Replace("'", "''");
        }
    }

    internal class PostgreSqlIntenalStringTypeMapping : PostgreSqlStringTypeMapping
    {
        public PostgreSqlIntenalStringTypeMapping()
            : base("varchar", NpgsqlDbType.Unknown)
        {
        }

        /// <summary>
        ///     This API supports the Entity Framework Core infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public override TypeMapping Clone(string dbTypeName, int? size)
        {
            return new PostgreSqlStringTypeMapping(dbTypeName, this.NpgsqlDbType);
        }

        /// <summary>
        ///     Generates the SQL representation of a literal value.
        /// </summary>
        /// <param name="value">The literal value.</param>
        /// <returns>
        ///     The generated string.
        /// </returns>
        protected override string GenerateNonNullSqlLiteral(object value)
        {
            return this.IsUnicode ?
                $"N'{this.EscapeSqlLiteral((string)value)}'" // Interpolation okay; strings
                : $"'{this.EscapeSqlLiteral((string)value)}'";
        }

        private string EscapeSqlLiteral([NotNull] string literal)
        {
            return Check.NotNull(literal, nameof(literal)).Replace("'", "''");
        }
    }
}
